Repository navigation
feat: add @metamask/profile-controller package - #10558
Conversation
…ations and rename getXAccount
… types and connectX signature
… and update state
… satisfy Json return type
mcmire
left a comment
There was a problem hiding this comment.
Looks great! Most comments are minor and can be deferred to another PR. Only big question is around whether or not it makes sense to include React hooks in the same package.
| * @param identifier - The profile identifier to fetch. | ||
| * @returns A TanStack Query result containing the profile data. | ||
| */ | ||
| export function useGetProfile( |
There was a problem hiding this comment.
Hmm... we have tried to keep APIs that can be used within the background/"engine" of the client and APIs that can be used on the UI side in separate packages. This way if you want to use the controller/service in a project that doesn't use React (e.g. the agentic wallet) you can do so. That's why @metamask/react-data-query is a separate package from @metamask/base-data-service, for instance.
What are your thoughts on creating two packages? Or maybe we can just put these hooks in the client for now and extract them to a new package later?
There was a problem hiding this comment.
dc46832 That makes sense to split into a diff package. I can do a follow up PR later with the separate package. Looping in @joaosantos15 since this was a request from their team.
|
@metamaskbot publish-preview |
MetaMask#10668) ## Explanation `@metamask/profile-controller` could start an X (Twitter) link, but the client had to supply the profile ID and hold the OAuth details. The Profile API now owns the whole X OAuth flow: it keeps the client secret, the PKCE verifier and the X tokens, and it creates the MetaMask profile during the connect if one doesn't exist yet (using the X handle as the username). The client only opens the authorize URL and relays the returned `code` and `state`. This PR updates the controller and service to match: - **`ProfileController.startXConnect({ linkedAddress? })`** (new) returns `{ authorizationUrl, state }`. Nothing is stored in controller state. The profile comes from the verified bearer token on the backend. `linkedAddress` (CAIP-10) is forwarded as `linked_address` and is required when the profile doesn't exist yet. - **`ProfileController.connectX({ code, state, profileId })`** (**BREAKING**) now returns `{ profile, xProfile, profileCreated }` instead of just the X profile. - It saves `xProfile` to state as soon as the connect succeeds, then fetches the profile, because the backend may have just created it. - If that fetch fails, the X link stays saved and a clear error is thrown. - **`ProfileController.disconnectX(profileId)`** (new) clears `xProfile` and sets `connectedToX` to `false`. - **`ProfileService.disconnectX(profileId)`** (new) sends `DELETE profiles/{profileId}/x`. A `404` is treated as success, meaning the account is already disconnected; otherwise a client whose saved state has drifted from the backend could never disconnect. - **`ProfileService.getXAuthUrl(linkedAddress?)`** sends the optional `linked_address` query parameter. - **`ProfileService.connectX`** accepts the optional `profile_created` boolean in the response. It's optional so older backends still validate. `startXConnect` and `disconnectX` are exposed on the messenger, and the method action types are regenerated. ## References - Builds on MetaMask#10558 (`@metamask/profile-controller`). - Mobile consumer PR: MetaMask/metamask-mobile#37160 - Related to https://consensyssoftware.atlassian.net/browse/MUL-2317?atlOrigin=eyJpIjoiYzRmMzRiZjY5MDY2NGY1NjljNjc3OGNkYjBkMmE1MGYiLCJwIjoiaiJ9 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking changes to X connect/createProfile state handling and OAuth entry requirements will affect mobile and other consumers; OAuth unlink uses authenticated DELETE with intentional 404-as-success semantics. > > **Overview** > **Adds X OAuth start and unlink APIs** on `ProfileController`/`ProfileService`, and tightens when X link state is written to controller state. > > `getXAuthorizationUrl` starts the flow: it requires a profile already in state, calls the backend for the authorize URL and `state`, and returns ephemeral `ConnectXSession` data without persisting OAuth details in the controller. `disconnectX(profileId)` calls `DELETE profiles/{profileId}/x` (404 treated as success) and clears `xProfile` / `connectedToX` when the ID matches state. > > **Breaking behavior changes:** `connectX` now requires a profile in state and, on success, immediately sets `xProfile` and `connectedToX` (it no longer relies on `createProfile` to carry over an `x_profile` from the create response). `getXAuthUrl` is bearer-token–scoped on the server with no `linkedAddress` parameter. > > Also fixes the messenger event typing to `ProfileController:stateChanged`, renames `XConnectResponse` → `ConnectXResponse`, and invalidates cached `getProfile` after connect/disconnect mutations in `ProfileService`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7ec47a5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Explanation
@metamask/profile-controller, a new package for managing MetaMask user profile stateProfileServicecommunicates with the MetaMask Profile API (profile.api.cx.metamask.io), exposinggetProfile,createProfile,replaceProfile,updateProfile,deleteProfile,checkUsernameAvailability,getXAuthUrl,connectX, andgetXAccountvia the messenger, with superstruct validation on all inputs and responsesProfileControllermanages profile state derived from the API and exposes all operations via the messenger; also exportsuseGetProfileanduseCheckUsernameAvailabilityReact hooks for UI components via@metamask/react-data-queryReferences
N/A
Checklist
Note
Medium Risk
Adds authenticated profile CRUD, persisted user state, and X OAuth flows; mistakes could affect account identity data, though inputs/outputs are struct-validated and heavily tested.
Overview
Introduces
@metamask/profile-controller, a new monorepo package for MetaMask user profiles (username, bio, avatar, linked addresses, trading privacy) and optional X (Twitter) linking.ProfileService(extendsBaseDataService) calls the MetaMask Profile API with bearer auth fromAuthenticationController:getBearerToken, validates requests/responses with superstruct, and exposes CRUD, username availability, and X OAuth (getXAuthUrl,connectX,getXAccount) via messenger actions.ProfileControllerkeeps persistedprofile/ optionalxProfilestate, maps API shapes to UI-friendly types, and delegates mutations toProfileService(including guards when no profile exists and clearing state on delete). Broad Jest coverage is included for both layers.Repo wiring adds CODEOWNERS (
@MetaMask/accounts-engineers), README/package graph entries, root/tsconfig references,teams.json, and oxlint suppression for the new jest config.Reviewed by Cursor Bugbot for commit dc46832. Bugbot is set up for automated code reviews on this repo. Configure here.